﻿using SCG.General;
using SCG.TurboSprite;
using SCG.TurboSprite.SquareGrid;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using TTEngine;

namespace TorchesTombs
{
    public partial class DungeonSpriteSurface : SquareGridSpriteSurface
    {
        //Constructors
        public DungeonSpriteSurface()
        {
            InitializeComponent();
            UseVirtualSize = true;
        }
        public DungeonSpriteSurface(IContainer container)
        {
            container.Add(this);
            InitializeComponent();
            UseVirtualSize = true;
        }

        //testing
        public void AddTestSprite(Sprite s)
        {
            engineThrown.AddSprite(s);
        }

        //events
        public event EventHandler<ActorEventArgs> ActorSelected;
        public event EventHandler<MapObjectListEventArgs> MapObjectsClicked;
        public event EventHandler<PointEventArgs> DropTargetClicked;

        //public properties
        public DungeonLevel Level { get; set; }

        public List<SCGPoint> DropTargets
        {
            get
            {
                return _dropTargets;
            }
        }

        //PC selected
        public Actor SelectedActor
        {
            get
            {
                return _selectedActor;
            }
            set
            {
                if (_selectedActor != null)
                {
                    MapObjectSprite mosOld = (MapObjectSprite)_selectedActor.Sprite;
                    mosOld.IsSelected = false;
                }
                _selectedActor = value;
                if (_selectedActor != null && _selectedActor.IsPC)
                {
                    MapObjectSprite mos = (MapObjectSprite)_selectedActor.Sprite;
                    mos.IsSelected = true;
                    if (ActorSelected != null)
                        ActorSelected(this, new ActorEventArgs(_selectedActor));
                }
            }
        }

        //setup sprites 
        public void Cleanup()
        {
            engineActors.Surface = this;
            engineMapObjects.Surface = this;
            engineMapObjects.Clear();
            engineActors.Clear();
        }

        //processing that should occur each impulse
        public void ImpulseProcessed()
        {
            //set sprite visibility
            foreach (MapObject mo in Level.MapObjects)
                if (mo.Sprite != null)
                {
                    MapObjectSprite mos = (MapObjectSprite)mo.Sprite;
                    mos.Visible = Level.VisibilityMatrix.Get(mo.X, mo.Y) && Level.IlluminationMatrix.Get(mo.X, mo.Y) > 0;
                }
        }

        //create drop targets based around a cell
        public void ClearDropTargets()
        {
            lock (_dropTargets)
            {
                _dropTargets.Clear();
            }
        }
        public void CreateDropTargets(int x, int y)
        {
            lock (_dropTargets)
            {
                _dropTargets.Clear();
                for(int y2 = -1; y2 <= 1; y2++)
                    for(int x2 = -1; x2 <= 1; x2++)
                        if (x2 != 0 || y2 != 0)
                            if (Level.IsOpenTerrain(x + x2, y + y2, true))
                            {
                                SCGPoint pt = new SCGPoint(x + x2, y + y2);
                                _dropTargets.Add(pt);
                            }
            }
        }
        public void CreateDropTargets(List<SCGPoint> pts)
        {
            lock (_dropTargets)
            {
                _dropTargets.Clear();
                _dropTargets.AddRange(pts);
            }
        }

        //throw an object
        public void ThrowObject(MapObject mo, int x1, int y1, int x2, int y2)
        {
            //create a sprite to represent the thrown object
            MapObjectSprite s = (MapObjectSprite)SpriteFactory.CreateSprite(mo);
            s.Tag = mo;
            s.X = x1 * 64 + 32;
            s.Y = y1 * 64 + 32;
            engineThrown.AddSprite(s);
            engineThrown.MoveSprite(s, new Point(x2 * 64 + 32, y2 * 64 + 32), 30);
        }

        //mouse handling
        protected override void OnMouseDown(MouseEventArgs e)
        {
            base.OnMouseDown(e);
            int cellX = (e.X + OffsetX) / 64;
            int cellY = (e.Y + OffsetY) / 64;
            lock (_dropTargets)
            {
                SCGPoint pt = new SCGPoint(cellX, cellY);
                if (_dropTargets.ContainsPoint(pt))
                {
                    _inhibitCellClick = true;
                    _inhibitSpriteClick = true;
                    if (DropTargetClicked != null)
                        DropTargetClicked(this, new PointEventArgs(pt));
                }
            }
        }
        protected override void OnMouseUp(MouseEventArgs e)
        {
            base.OnMouseUp(e);
            _inhibitSpriteClick = false;
            _inhibitSpriteClick = false;
        }

        //clicked on a cell
        protected override void DoCellClicked(int x, int y, MouseButtons mb)
        {
            if (_inhibitCellClick)
            {
                _inhibitCellClick = false;
                return;
            }

            ClearDropTargets();

            //is someone selected?
            if (SelectedActor != null)
            {
                //move them here
                if (SelectedActor.DestX != x || SelectedActor.Y != y)
                {
                    //are we adjacent?
                    if (SelectedActor.AdjacentTo(x, y))
                    {
                        //can we climb to that target?
                        if (CheckClimb(x, y))
                            return;
                    }

                    //move to that space
                    SelectedActor.DestX = x;
                    SelectedActor.DestY = y;
                    SelectedActor.Orders = OrderType.Move;
                    SelectedActor = null;
                }
                CenterOnCell(x, y);
            }
            else
            {
                //no, center here
                CenterOnCell(x, y);
            }
        }

        //see if we can climb
        private bool CheckClimb(int x, int y)
        {
            if (Level.TerrainLevel(SelectedActor.X, SelectedActor.Y) != Level.TerrainLevel(x, y))
            {
                //create a dummy platform target to trigger the climb action
                ClimbTarget ct = new ClimbTarget();
                ct.Level = Level;
                ct.X = x;
                ct.Y = y;
                List<MapObject> lst = new List<MapObject>();
                lst.Add(ct);
                if (MapObjectsClicked != null)
                    MapObjectsClicked(this, new MapObjectListEventArgs(x, y, lst));
                return true;
            }
            return false;
        }

        //clicked a sprite
        protected override void DoSpriteClick(Sprite sprite)
        {
            if (_inhibitSpriteClick)
            {
                _inhibitSpriteClick = false;
                return;
            }

            ClearDropTargets();
            MapObjectSprite mos = (MapObjectSprite)sprite;
            MapObject mo = mos.MapObject;

            //is a PC currently selected?
            if (SelectedActor != null)
            {
                //yes, inhibit next cell-click (move) event
                _inhibitCellClick = true;

                //clicked another actor?
                if (mo.IsActor)
                {
                    //another PC?  If so, escort
                    Actor target = (Actor)mo;
                    if (target.IsPC)
                    {
                        if (target != SelectedActor)
                        {
                            SelectedActor.DestX = SelectedActor.X - target.X;
                            SelectedActor.DestY = SelectedActor.Y - target.Y;
                            SelectedActor.Target = target;
                            SelectedActor.Orders = OrderType.Escort;
                        }
                        SelectedActor = null;
                    }
                }

                //are they adjacent to the clicked sprite?
                else if (SelectedActor.AdjacentTo(mo.Position))
                {
                    //now, see if they clicked an actor
                    if (mo.IsActor)
                    {
                        //TODO Selected actor clicked another actor, escort or attack
                        //for now, just de-select the currently selected actor
                        SelectedActor = null;
                    }
                    else
                    {
                        //see if it's a climb
                        if (CheckClimb(mo.X, mo.Y))
                            return;

                        //notify UI that a PC clicked next to map-opbjects
                        List<MapObject> lst = Level.ObjectMatrix[mo.X, mo.Y];
                        if (lst != null)
                        {
                            if (MapObjectsClicked != null)
                                MapObjectsClicked(this, new MapObjectListEventArgs(mo.X, mo.Y, lst));

                            //if there are no actions, we can allow the actor to move there
                            foreach (MapObject moAction in lst)
                            {
                                List<string> actions = moAction.GetActionsAdjacent(SelectedActor);
                                if (actions.Count > 0)
                                    return;
                            }
                            _inhibitCellClick = false;
                        }
                        else
                            _inhibitCellClick = false;
                    }
                }
                else
                    _inhibitCellClick = false;
            }
            else
            {
                //no, we can select this one
                if (mos.MapObject.IsActor)
                {
                    Actor actor = (Actor)mos.MapObject;
                    if (actor.IsPC)
                    {
                        SelectedActor = actor;
                        _inhibitCellClick = true;

                        //show popup for any items in their square
                        List<MapObject> lst = Level.ObjectMatrix[SelectedActor.X, SelectedActor.Y];
                        lst.Remove(SelectedActor);
                        if (lst.Count > 0)
                        {
                            if (MapObjectsClicked != null)
                                MapObjectsClicked(this, new MapObjectListEventArgs(SelectedActor.X, SelectedActor.Y, lst));
                        }
                    }
                }
            }
        }

        //move a sprite
        public void MoveSprite(MapObjectSprite mos, int x, int y, float speed)
        {
            mos.Mode = MoveMode.Normal;
            engineActors.MoveSprite(mos, new Point(x * 64 + 32, y * 64 + 32), 10);
        }

        //add a sprite
        public void SpriteAdded(MapObject mo)
        {
            MapObjectSprite mos = (MapObjectSprite)mo.Sprite;
            mos.Position = new Point(mo.X * 64 + 32, mo.Y * 64 + 32);
            if (mo.IsActor)
                engineActors.AddSprite(mos);
            else
                engineMapObjects.AddSprite(mos);
        }

        //remove a sprite
        public void SpriteRemoved(MapObject mo)
        {
            MapObjectSprite mos = (MapObjectSprite)mo.Sprite;
            mos.Kill();
        }

        //render tiles
        protected override void DoBeforeSpriteRender(Graphics g)
        {
            base.DoBeforeSpriteRender(g);
            if (Level == null)
                return;

            //set up bitmap factories
            if (tiles.MasterBitmap == null)
            {
                tiles.MasterBitmap = Level.Theme.TileGraphic;
                tiles.CellsX = tiles.MasterBitmap.Width / 64;
                tiles.CellsY = tiles.MasterBitmap.Height / 64;
                walls.MasterBitmap = Level.Theme.WallGraphic;
                walls.CellsX = walls.MasterBitmap.Width / 64;
                walls.CellsY = walls.MasterBitmap.Height / 64;
            }

            //render tiles
            int cy = OffsetCellY;
            int ty = 0;
            while (cy < Level.Height)
            {
                if (ty > Height)
                    break;
                int cx = OffsetCellX;
                int tx = 0;
                while (cx < Level.Width)
                {
                    if (tx > Width)
                        break;
                    if (!Level.VisibilityMatrix.Get(cx, cy) || Level.IlluminationMatrix.Get(cx, cy) == 0)
                    {
                        g.FillRectangle(_blackBrush, tx, ty, 64, 64);
                    }
                    else
                    {
                        int offY = 0;
                        char c = Level.TerrainMap.Get(cx, cy);
                        byte light = Level.IlluminationMatrix.Get(cx, cy);
                        Bitmap bmp;
                        if (c == Globals.TERRAIN_WALL)
                        {
                            int wallx = cx * 127 % walls.CellsX;
                            int wally = cy * 721 % walls.CellsY;
                            bmp = walls.GetGamePieceBitmap(wallx, wally, _lightColors[light]);
                        }
                        else
                        {
                            int tilex = cx * 127 % tiles.CellsX;
                            int tiley = cy * 721 % tiles.CellsY;
                            if (c == Globals.TERRAIN_PLATFORM)
                                offY = -4;
                            else if (c == Globals.TERRAIN_PIT)
                                offY = 4;
                            bmp = tiles.GetGamePieceBitmap(tilex, tiley, _lightColors[light]);
                        }
                        if (offY != 0)
                            g.DrawImage(bmp, tx, ty);
                        g.DrawImage(bmp, tx, ty + offY);

                        //lighten platform squares, darken pits
                        if (c == Globals.TERRAIN_PLATFORM)
                            g.FillRectangle(_platformBrush, tx, ty + offY, 64, 64);
                        else if (c == Globals.TERRAIN_PIT)
                            g.FillRectangle(_pitBrush, tx, ty + offY, 64, 64);
                    }
                    cx++;
                    tx += 64;
                }
                cy++;
                ty += 64;
            }
        }

        //render drop targets
        protected override void DoAfterSpriteRender(Graphics g)
        {
            base.DoAfterSpriteRender(g);

            if (_dropTargetPen == null)
            {
                _dropTargetPen = new Pen(Color.FromArgb(64, 128, 255, 128));
                _dropTargetPen.Width = 12;
            }

            lock (_dropTargets)
            {
                foreach (SCGPoint pt in _dropTargets)
                {
                    int dx = pt.X * 64 - OffsetX;
                    int dy = pt.Y * 64 - OffsetY;
                    g.DrawEllipse(_dropTargetPen, dx, dy, 64, 64);
                }
            }
        }

        //thrown object reached destination
        private void engineThrown_SpriteReachedDestination(object sender, SpriteEventArgs e)
        {
            if (e.Sprite.Tag == null)
                return;
            MapObject mo = (MapObject)e.Sprite.Tag;
            e.Sprite.Kill();
            mo.Sprite = null;
            Level.ThrownObjectLands(mo, (int)(e.Sprite.X / 64), (int)(e.Sprite.Y / 64));
        }

        //sprite explodes in specified space
        public void SpriteExplodes(MapObject mo, int x, int y)
        {
            //create a new explosion sprite to represent this
            Bitmap bmp = mo.Image;
            bmp.MakeTransparent(Color.Fuchsia);
            BreakingBitmapExplosionSprite bes = new BreakingBitmapExplosionSprite(bmp, Globals.RandomIntFromTo(4, 8), 8, 16, Globals.RandomIntFromTo(20, 40));
            bes.X = x * 64 + 32;
            bes.Y = y * 64 + 32;
            engineThrown.AddSprite(bes);
        }

        //make a blood splatter effect
        public void BloodSplatter(int x, int y)
        {
            ParticleExplosionSprite es = new ParticleExplosionSprite(Globals.RandomIntFromTo(4, 8), Color.Black, Color.Red, 3, 6, Globals.RandomIntFromTo(10, 15));
            es.X = x * 64 + 32;
            es.Y = y * 64 + 32;
            engineThrown.AddSprite(es);
        }

        //make a sprite lunge toward another sprite, then return
        public void LungeSprite(MapObject mo, MapObject target)
        {
            if (mo.Sprite == null || target.Sprite == null)
                return;
            MapObjectSprite s1 = (MapObjectSprite)mo.Sprite;
            MapObjectSprite s2 = (MapObjectSprite)target.Sprite;
            s1.Mode = MoveMode.Lunge;
            int x = (int)((s1.X + s2.X) / 2.0f);
            int y = (int)((s1.Y + s2.Y) / 2.0f);
            engineActors.MoveSprite(s1, new Point(x, y), 15);
        }

        //make a sprite shift around its location
        public void ShuffleSprite(MapObject mo)
        {
            if (mo.Sprite == null)
                return;
            MapObjectSprite s = (MapObjectSprite)mo.Sprite;
            s.Mode = MoveMode.Work;
            int x = mo.X * 64 + 32 + Globals.RandomIntFromTo(-8, 8);
            int y = mo.Y * 64 + 32 + Globals.RandomIntFromTo(-8, 8);
            engineActors.MoveSprite(s, new Point(x, y), Globals.RandomIntFromTo(2, 8));
        }

        //make a sprite shuffle between its current location and its "work" location
        public void ShuffleSprite(MapObject mo, int x, int y)
        {
            if (mo.Sprite == null)
                return;
            MapObjectSprite mos = (MapObjectSprite)mo.Sprite;
            mos.WorkX = x;
            mos.WorkY = y;
            int sx = (int)(((mo.X + x) / 2.0) * 64 + 32) + Globals.RandomIntFromTo(-8, 8);
            int sy = (int)(((mo.Y + y) / 2.0) * 64 + 32) + Globals.RandomIntFromTo(-8, 8);
            mos.Mode = MoveMode.WorkAt;
            engineActors.MoveSprite(mos, new Point(sx, sy), Globals.RandomIntFromTo(2, 8));
        }

        //complete a lunge, or work movement
        private void engineActors_SpriteReachedDestination(object sender, SpriteEventArgs e)
        {
            MapObjectSprite s = (MapObjectSprite)e.Sprite;
            if (s.Mode == MoveMode.Lunge)
            {
                MapObject mo = s.MapObject;
                s.Mode = MoveMode.Normal;
                engineActors.MoveSprite(s, new Point(mo.X * 64 + 32, mo.Y * 64 + 32), 15);
            }
            else if (s.Mode == MoveMode.Work)
            {
                ShuffleSprite(s.MapObject);
            }
            else if (s.Mode == MoveMode.WorkAt)
            {
                ShuffleSprite(s.MapObject, s.WorkX, s.WorkY);
            }
        }

        //private members
        private SolidBrush _blackBrush = new SolidBrush(Color.Black);
        private SolidBrush _platformBrush = new SolidBrush(Color.FromArgb(96, 255, 255, 255));
        private SolidBrush _pitBrush = new SolidBrush(Color.FromArgb(96, 0, 0, 0));
        private Color[] _lightColors = { Color.Black, Color.FromArgb(100, 100, 100), Color.FromArgb(150, 150, 150), Color.FromArgb(200, 200, 200), Color.White };
        private Actor _selectedActor = null;
        private bool _inhibitCellClick = false;
        private bool _inhibitSpriteClick = false;
        private List<SCGPoint> _dropTargets = new List<SCGPoint>();
        private Pen _dropTargetPen;
    }
}
